﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OWLServer.owl.tipos;
using System.Xml.Linq;
using OWLServer.owl.etiquetas;

namespace OWLServer.owl.utilidades
{
    /// <summary>
    /// Clase que contiene métodos para manejar las restricciones que se aplican a una clase.
    /// </summary>
    class UtilidadesRestricciones
    {
        /// <summary>
        /// Construye las restricciones de las clases del OWL
        /// </summary>
        /// <param name="ontologia">Ontología</param>
        /// <param name="root">Raiz del documento OWL</param>
        /// <param name="propiedadVinculada">Propiedad con la que vamos a vincular la restriccion en la clase (subClase o claseEquivalente)</param>
        public static void construirRestricciones(Ontologia ontologia, XElement root)
        {
            // Restricciones de Clases
            construirRestriccionesClases(ontologia, root);

            // Restricciones de Propiedades Objeto
            construirRestriccionesPropiedadesObjeto(ontologia, root);

            // Restricciones de Propiedades Dato
            construirRestriccionesPropiedadesDato(ontologia, root, TipoPropiedadRelacionada.PROPIEDAD_DATO_DOMINIO);
        }

        /// <summary>
        /// Construye las restricciones de las clases del OWL. (dominio o rango)
        /// </summary>
        /// <param name="ontologia">Ontología</param>
        /// <param name="root">Raiz del documento OWL</param>
        private static void construirRestriccionesClases(Ontologia ontologia, XElement root)
        {
            if (UtilidadesEspaciosNombres.contieneOWL(ontologia.getEspaciosNombresDictiorary()) && UtilidadesEspaciosNombres.contieneRDFS(ontologia.getEspaciosNombresDictiorary()))
            {
                // Vemos a la propiedad vinculada a la que corresponde.
                XName propiedadVinculadaSubclase = UtilidadesEspaciosNombres.getEspacioNombres(ontologia.getEspaciosNombresDictiorary()[EtiquetasRDFS.NOMBRE]) + EtiquetasRDFS.SUBCLASE;
                XName propiedadVinculadaEquivalente = UtilidadesEspaciosNombres.getEspacioNombres(ontologia.getEspaciosNombresDictiorary()[EtiquetasOWL.NOMBRE]) + EtiquetasOWL.CLASE_EQUIVALENTE;
                Elemento about;
                Clase clase;

                // Extraemos los elementos de las clases que tengan subclases o equivalentes
                IEnumerable<XElement> eleClases =
                    from el in root.Elements(UtilidadesEspaciosNombres.getEspacioNombres(ontologia.getEspaciosNombresDictiorary()[EtiquetasOWL.NOMBRE]) + EtiquetasOWL.CLASE)
                    where el.Elements(propiedadVinculadaEquivalente).Count() > 0
                    || el.Elements(propiedadVinculadaSubclase).Count() > 0
                    select el;

                // Recorremos los elementos de tipo Clase
                foreach (XElement ele in eleClases)
                {
                    about = UtilidadesElementos.getElemento(UtilidadesElementos.getId(ele, ontologia.getEspaciosNombresDictiorary()), ontologia);
                    clase = UtilidadesClases.getClase(ontologia, about);                    

                    añadirRestriccionesElemento(ontologia, ele, propiedadVinculadaSubclase, clase, TipoPropiedadRelacionada.CLASE_SUBCLASE);
                    añadirRestriccionesElemento(ontologia, ele, propiedadVinculadaEquivalente, clase, TipoPropiedadRelacionada.CLASE_EQUIVALENTE);
                }
            }
        }

        /// <summary>
        /// Construye las restricciones de las propiedades de tipo dato del OWL
        /// </summary>
        /// <param name="ontologia">Ontología</param>
        /// <param name="root">Raiz del documento OWL</param>
        /// <param name="tipoPropiedadRelacionada">Tipo de propiedad a la que vamos a aplicar la restricción, dentro de la clase. (dominio o rango)</param>
        private static void construirRestriccionesPropiedadesDato(Ontologia ontologia, XElement root, TipoPropiedadRelacionada tipoPropiedadRelacionada)
        {
            if (UtilidadesEspaciosNombres.contieneOWL(ontologia.getEspaciosNombresDictiorary()) && UtilidadesEspaciosNombres.contieneRDFS(ontologia.getEspaciosNombresDictiorary()))
            {
                // Vemos a la propiedad vinculada a la que corresponde.
                XName propiedadVinculadaDominio = UtilidadesEspaciosNombres.getEspacioNombres(ontologia.getEspaciosNombresDictiorary()[EtiquetasRDFS.NOMBRE]) + EtiquetasRDFS.DOMINIO; ;
                Elemento about;
                Propiedad propiedad;

                // Extraemos los elementos de las propiedades con dominio
                IEnumerable<XElement> elePropiedad =
                    from el in root.Elements(UtilidadesEspaciosNombres.getEspacioNombres(ontologia.getEspaciosNombresDictiorary()[EtiquetasOWL.NOMBRE]) + EtiquetasOWL.PROPIEDAD_DATOS)
                    where el.Elements(propiedadVinculadaDominio).Count() > 0
                    select el;

                // Recorremos los elementos de tipo Clase
                foreach (XElement ele in elePropiedad)
                {
                    about = UtilidadesElementos.getElemento(UtilidadesElementos.getId(ele, ontologia.getEspaciosNombresDictiorary()), ontologia);
                    propiedad = UtilidadesPropiedades.getPropiedadDato(ontologia, about);

                    añadirRestriccionesElemento(ontologia, ele, propiedadVinculadaDominio, propiedad, TipoPropiedadRelacionada.PROPIEDAD_DATO_DOMINIO);
                }
            }
        }

        /// <summary>
        /// Construye las restricciones de las propiedades de tipo objeto del OWL. (subClase o claseEquivalente)
        /// </summary>
        /// <param name="ontologia">Ontología</param>
        /// <param name="root">Raiz del documento OWL</param>
        private static void construirRestriccionesPropiedadesObjeto(Ontologia ontologia, XElement root)
        {
            if (UtilidadesEspaciosNombres.contieneOWL(ontologia.getEspaciosNombresDictiorary()) && UtilidadesEspaciosNombres.contieneRDFS(ontologia.getEspaciosNombresDictiorary()))
            {
                XName propiedadVinculadaDominio = propiedadVinculadaDominio = UtilidadesEspaciosNombres.getEspacioNombres(ontologia.getEspaciosNombresDictiorary()[EtiquetasRDFS.NOMBRE]) + EtiquetasRDFS.DOMINIO;
                XName propiedadVinculadaRango = UtilidadesEspaciosNombres.getEspacioNombres(ontologia.getEspaciosNombresDictiorary()[EtiquetasRDFS.NOMBRE]) + EtiquetasRDFS.RANGO; ;
                Elemento about;
                Propiedad propiedad;

                // Extraemos los elementos de las propiedades con dominio o rango
                IEnumerable<XElement> elePropiedad =
                    from el in root.Elements(UtilidadesEspaciosNombres.getEspacioNombres(ontologia.getEspaciosNombresDictiorary()[EtiquetasOWL.NOMBRE]) + EtiquetasOWL.PROPIEDAD_OBJETO)
                    where el.Elements(propiedadVinculadaDominio).Count() > 0
                    || el.Elements(propiedadVinculadaDominio).Count() > 0
                    select el;

                // Recorremos los elementos de tipo Propiedad
                foreach (XElement ele in elePropiedad)
                {
                    about = UtilidadesElementos.getElemento(UtilidadesElementos.getId(ele, ontologia.getEspaciosNombresDictiorary()), ontologia);
                    propiedad = UtilidadesPropiedades.getPropiedadObjeto(ontologia, about);
                    

                    añadirRestriccionesElemento(ontologia, ele, propiedadVinculadaDominio, propiedad, TipoPropiedadRelacionada.PROPIEDAD_OBJETO_DOMINIO);
                    añadirRestriccionesElemento(ontologia, ele, propiedadVinculadaRango, propiedad, TipoPropiedadRelacionada.PROPIEDAD_OBJETO_RANGO);
                }
            }
        }

        /// <summary>
        /// Añade las restricciones al elemento.
        /// </summary>
        /// <param name="ontologia">Ontología</param>
        /// <param name="ele">Elemento XML que representa al elemento de la ontologia</param>
        /// <param name="propiedadVinculada">Propiedad vinculada al elemento de la que vamos a buscar las restricciones</param>
        /// <param name="elemento">Elemento al que vamos a buscar las restricciones</param>
        /// <param name="tipoPropiedadRelacionada">Tipo de propiedad a la que vamos a aplicar la restricción.</param>
        private static void añadirRestriccionesElemento(Ontologia ontologia, XElement ele, XName propiedadVinculada, ElementoRestringido elemento, TipoPropiedadRelacionada tipoPropiedadRelacionada)
        {
            // Recorremos los elementos de la propiedad vinculada a la clase que tienen definidas las restricciones
            IEnumerable<XElement> elesPropiedadVinculada =
                from el in ele.Elements(propiedadVinculada)
                where el.Elements(UtilidadesEspaciosNombres.getEspacioNombres(ontologia.getEspaciosNombresDictiorary()[EtiquetasOWL.NOMBRE]) + EtiquetasOWL.RESTRICCION).Count() > 0
                select el;

            foreach (XElement elePripiedadVincu in elesPropiedadVinculada)
            {
                añadirRestriccionElemento(ontologia, elePripiedadVincu, elemento, tipoPropiedadRelacionada);
            }
        }

        /// <summary>
        /// Añade la restriccion al elemento.
        /// </summary>
        /// <param name="ontologia">Ontología</param>
        /// <param name="elePropiedadVincu">Elemento XML que representa a la propievada vinculada, que puede tener las restricciones.</param>
        /// <param name="propiedadVinculada">Propiedad vinculada al elemento de la que vamos a buscar las restricciones</param>
        /// <param name="elemento">Elemento al que vamos a buscar las restricciones</param>
        /// <param name="tipoPropiedadRelacionada">Tipo de propiedad a la que vamos a aplicar la restricción.</param>
        private static void añadirRestriccionElemento(Ontologia ontologia, XElement elePropiedadVincu, ElementoRestringido elemento, TipoPropiedadRelacionada tipoPropiedadRelacionada)
        {
            Restriccion restriccion = null;
            Elemento recursoPropiedad = null;
            IEnumerable<XElement> eleEnPropiedades;            


            // Recorremos los elementos de las subclases que tienen definidas las restricciones
            IEnumerable<XElement> eleRestricciones =
                from el in elePropiedadVincu.Elements(UtilidadesEspaciosNombres.getEspacioNombres(ontologia.getEspaciosNombresDictiorary()[EtiquetasOWL.NOMBRE]) + EtiquetasOWL.RESTRICCION)
                select el;

            foreach (XElement eleRest in eleRestricciones)
            {
                recursoPropiedad = null;

                // Vemos en la propiedad en la que se va a aplicar
                eleEnPropiedades =
                    from el in eleRest.Elements(UtilidadesEspaciosNombres.getEspacioNombres(ontologia.getEspaciosNombresDictiorary()[EtiquetasOWL.NOMBRE]) + EtiquetasOWL.EN_PROPIEDAD)
                    select el;

                foreach (XElement eleEnProp in eleEnPropiedades)
                {
                    recursoPropiedad = UtilidadesElementos.getElemento(UtilidadesElementos.getNombreRecurso(eleEnProp, ontologia.getEspaciosNombresDictiorary()), ontologia);
                }

                // Miramos si es una restriccion que no sea de cardinalidad
                restriccion = extraerRestricionesNoCardinalidad(ontologia, eleRest, recursoPropiedad, tipoPropiedadRelacionada, TipoRestriccion.ALGUNOS_VALORES_DE, EtiquetasOWL.ALGUNOS_VALORES_DE);
                if (null == restriccion)
                {
                    restriccion = extraerRestricionesNoCardinalidad(ontologia, eleRest, recursoPropiedad, tipoPropiedadRelacionada, TipoRestriccion.TIENE_UN_VALOR, EtiquetasOWL.TIENE_UN_VALOR);
                    if (null == restriccion)
                    {
                        restriccion = extraerRestricionesNoCardinalidad(ontologia, eleRest, recursoPropiedad, tipoPropiedadRelacionada, TipoRestriccion.TODOS_LOS_VALORES_DE, EtiquetasOWL.TODOS_LOS_VALORES_DE);
                    }
                }

                // Segun el tipo de popiedad obtenemos unas restriccion u otras
                if (null == restriccion)
                {
                    // Miramos las restricciones de cardinalidad asociadas a Propiedades de Objeto
                    restriccion = extraerRestricionesCardinalidad(ontologia, eleRest, recursoPropiedad, tipoPropiedadRelacionada, TipoRestriccion.CARDINALIDAD, EtiquetasOWL.CARDINALIDAD_PROPIEDAD_OBJETO);
                    if (null == restriccion)
                    {
                        restriccion = extraerRestricionesCardinalidad(ontologia, eleRest, recursoPropiedad, tipoPropiedadRelacionada, TipoRestriccion.CARDINALIDAD_MAXIMA, EtiquetasOWL.CARDINALIDAD_MAXIMA_PROPIEDAD_OBJETO);
                        if (null == restriccion)
                        {
                            restriccion = extraerRestricionesCardinalidad(ontologia, eleRest, recursoPropiedad, tipoPropiedadRelacionada, TipoRestriccion.CARDINALIDAD_MINIMA, EtiquetasOWL.CARDINALIDAD_MINIMA_PROPIEDAD_OBJETO);
                        }
                    }

                    // Si no es de Objeto, miramos las restricciones de cardinalidad asociadas a Propiedades de Dato
                    if (null == restriccion)
                    {
                        restriccion = extraerRestricionesCardinalidad(ontologia, eleRest, recursoPropiedad, tipoPropiedadRelacionada, TipoRestriccion.CARDINALIDAD, EtiquetasOWL.CARDINALIDAD_PROPIEDAD_DATO);
                        if (null == restriccion)
                        {
                            restriccion = extraerRestricionesCardinalidad(ontologia, eleRest, recursoPropiedad, tipoPropiedadRelacionada, TipoRestriccion.CARDINALIDAD_MAXIMA, EtiquetasOWL.CARDINALIDAD_MAXIMA_PROPIEDAD_DATO);
                            if (null == restriccion)
                            {
                                restriccion = extraerRestricionesCardinalidad(ontologia, eleRest, recursoPropiedad, tipoPropiedadRelacionada, TipoRestriccion.CARDINALIDAD_MINIMA, EtiquetasOWL.CARDINALIDAD_MINIMA_PROPIEDAD_DATO);
                            }
                        }
                    }
                    else
                    {
                        // Extraemos la clase asociada a la restriccion de propiedad objeto
                        restriccion.Clase = extraerClaseRestriccionObjeto(ontologia, eleRest);
                    }
                }

                // Guardamos la restriccion segun el tipo
                if (null != restriccion)
                {
                    if (restriccion is RestriccionPropiedadDato)
                    {
                        // La agregamos a la lista
                        elemento.RestriccionesPropiedadDato.Add(restriccion as RestriccionPropiedadDato);
                    }
                    else if (restriccion is RestriccionPropiedadObjeto)
                    {
                        // La agregamos a la lista
                        elemento.RestriccionesPropiedadObjeto.Add(restriccion as RestriccionPropiedadObjeto);
                    }
                }
            }
        }

        /// <summary>
        /// Extrae la restriccion que no sea de cardenalidad. 
        /// </summary>
        /// <param name="ontologia">Ontología</param>
        /// <param name="eleRest">Elemento del OWL que representa la restriccion</param>
        /// <param name="recursoPropiedad">Elemento que representa a la propiedad</param>
        /// <returns>Retorna la restriccion</returns>
        /// <param name="tipoPropiedadRelacionada">Tipo de propiedad a la que vamos a aplicar la restricción.</param>
        /// <param name="estiquetaOWLAtributo">Etiqueta owl que nombra al atributo que vamos abuscar. (allValuesFrom, someValuesFrom o hasValue)</param>
        private static Restriccion extraerRestricionesNoCardinalidad(Ontologia ontologia, XElement eleRest, Elemento recursoPropiedad, TipoPropiedadRelacionada tipoPropiedadRelacionada, TipoRestriccion tipoRestriccion, String estiquetaOWLAtributo)
        {
            Restriccion restriccion = null;
            Elemento recurso;
            Clase clase = null;

            IEnumerable<XElement> eleTodosLosValoresDe =
                from el in eleRest.Elements(UtilidadesEspaciosNombres.getEspacioNombres(ontologia.getEspaciosNombresDictiorary()[EtiquetasOWL.NOMBRE]) + estiquetaOWLAtributo)
                select el;

            foreach (XElement eleTipo in eleTodosLosValoresDe)
            {
                recurso = UtilidadesElementos.getElemento(UtilidadesElementos.getNombreRecurso(eleTipo, ontologia.getEspaciosNombresDictiorary()), ontologia);
                clase = null;
                if (recurso != null) // Si es null es porque es una restricción compleja y tiene mas anidadas
                {
                    if (tipoRestriccion != TipoRestriccion.TIENE_UN_VALOR)
                    {
                        if (ontologia.getClasesDictiorary().ContainsKey(recurso.ToString())) // Si es una clase lo que tiene asociado es una propiedad de objeto
                        {
                            clase = UtilidadesClases.getClase(ontologia, recurso);
                            restriccion = new RestriccionPropiedadObjeto(UtilidadesPropiedades.getPropiedadObjeto(ontologia, recursoPropiedad), tipoRestriccion, tipoPropiedadRelacionada, clase);
                        }
                        else // Si no es una clase lo que tiene asociado es una propiedad de dato
                        { // En el caso de ser un tipo Valor (XSD, RDF, RDFS, OWL...) se omite la clase
                            restriccion = new RestriccionPropiedadDato(UtilidadesPropiedades.getPropiedadDato(ontologia, recursoPropiedad), tipoRestriccion, tipoPropiedadRelacionada, clase);
                        }
                    }
                }
            }

            return restriccion;
        }

        /// <summary>
        /// Extrae la restriccion que sea de cardenalidad. 
        /// </summary>
        /// <param name="ontologia">Ontología</param>
        /// <param name="eleRest">Elemento del OWL que representa la restriccion</param>
        /// <param name="recursoPropiedad">Elemento que representa a la propiedad</param>
        /// <returns>Retorna la restriccion</returns>
        /// <param name="tipoPropiedadRelacionada">Tipo de propiedad a la que vamos a aplicar la restricción.</param>
        /// <param name="etiquetaOWLAtributo">Etiqueta owl que nombra al atributo que vamos abuscar. (allValuesFrom, someValuesFrom o hasValue)</param>
        private static Restriccion extraerRestricionesCardinalidad(Ontologia ontologia, XElement eleRest, Elemento recursoPropiedad, TipoPropiedadRelacionada tipoPropiedadRelacionada, TipoRestriccion tipoRestriccion, String etiquetaOWLAtributo)
        {
            Restriccion restriccion = null;

            IEnumerable<XElement> elemen =
                from el in eleRest.Elements(UtilidadesEspaciosNombres.getEspacioNombres(ontologia.getEspaciosNombresDictiorary()[EtiquetasOWL.NOMBRE]) + etiquetaOWLAtributo)
                select el;

            foreach (XElement eleTipo in elemen)
            {
                if (isCardionalidadObjeto(etiquetaOWLAtributo, ontologia, recursoPropiedad))
                {
                    restriccion = new RestriccionPropiedadObjeto(UtilidadesPropiedades.getPropiedadObjeto(ontologia, recursoPropiedad), tipoRestriccion, tipoPropiedadRelacionada, int.Parse(eleTipo.Value));
                }
                else
                {
                    restriccion = new RestriccionPropiedadDato(UtilidadesPropiedades.getPropiedadDato(ontologia, recursoPropiedad), tipoRestriccion, tipoPropiedadRelacionada, int.Parse(eleTipo.Value));
                }
            }

            return restriccion;
        }

        /// <summary>
        /// Segun el atributo de cardionalidad de la restricción nos dice si es de Objeto o no
        /// </summary>
        /// <param name="etiquetaOWLAtributo">Etiqueta que indica la cardinalidad que estamos recuperando</param>
        /// <param name="ontologia">Ontología</param>
        /// <param name="recursoPropiedad">Elemento que representa a la propiedad</param>
        /// <returns></returns>
        private static bool isCardionalidadObjeto(String etiquetaOWLAtributo, Ontologia ontologia, Elemento recursoPropiedad)
        {
            return EtiquetasOWL.CARDINALIDAD_MAXIMA_PROPIEDAD_OBJETO.Equals(etiquetaOWLAtributo)
                || EtiquetasOWL.CARDINALIDAD_MINIMA_PROPIEDAD_OBJETO.Equals(etiquetaOWLAtributo)
                || EtiquetasOWL.CARDINALIDAD_PROPIEDAD_OBJETO.Equals(etiquetaOWLAtributo)
                || ontologia.PropiedadesObjetos.Contains(new Propiedad(recursoPropiedad));
        }

        /// <summary>
        /// Extrae la clase con la que se restringe de propiedad objeto. 
        /// </summary>
        /// <param name="ontologia">Ontología</param>
        /// <param name="eleRest">Elemento del OWL que representa la restriccion</param>
        private static Clase extraerClaseRestriccionObjeto(Ontologia ontologia, XElement eleRest)
        {
            Clase clase = null;
            Elemento recurso;

            IEnumerable<XElement> elemen =
                from el in eleRest.Elements(UtilidadesEspaciosNombres.getEspacioNombres(ontologia.getEspaciosNombresDictiorary()[EtiquetasOWL.NOMBRE]) + EtiquetasOWL.EN_CLASE)
                select el;

            foreach (XElement eleTipo in elemen)
            {
                recurso = UtilidadesElementos.getElemento(UtilidadesElementos.getNombreRecurso(eleTipo, ontologia.getEspaciosNombresDictiorary()), ontologia);
                clase = UtilidadesClases.getClase(ontologia, recurso);
            }

            return clase;
        }

        /// <summary>
        /// Construye las restricciones de las clases del OWL. (tipo)
        /// </summary>
        /// <param name="ontologia">Ontología</param>
        /// <param name="root">Raiz del documento OWL</param>
        public static void construirRestriccionesIndividuo(Ontologia ontologia, XElement eleTipoIndividuo, Individuo individuo)
        {
            if (UtilidadesEspaciosNombres.contieneOWL(ontologia.getEspaciosNombresDictiorary()) && UtilidadesEspaciosNombres.contieneRDFS(ontologia.getEspaciosNombresDictiorary()))
            {
                // Buscamos las restricciones en el tipo
                añadirRestriccionElemento(ontologia, eleTipoIndividuo, individuo, TipoPropiedadRelacionada.INDIVIDUO_TIPO);
            }
        }
    }
}
